﻿/***********************************************************************************************
 * 
 * ProjectName: WMSSoft.Lib.Shell
 * 
 * Description: Provides Members needed for Shell functionality
 * 
 * Created:     06.01.2014
 * 
 * Author:      matthias wagler - matthias@WMSSoft.de
 * 
 * Copyright:   Copyright 2012 by WMSSoft.net
 * 
 * License:     GPLv3
 * 
 * History:     06.01.2014 - matthias wagler matthias@WMSSoft.de   -   Created
 * 
 **********************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32;


namespace WMSSoft.Lib.Shell
{
    public delegate void FavoriteEventArgs(string File, int LinkType);

    public static class Shell_helpers
    {        
        private static IContextMenu2 _iContextMenu2;
        private static IContextMenu3 _iContextMenu3;

        private static int cbInvokeCommand = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX));

        public static event FavoriteEventArgs AddFavorite;
        public static event FavoriteEventArgs DeleteFavorite;
        public static event FavoriteEventArgs ContextMenuActivated;

        /// <summary>
        /// This Methode gets an Message for given ShellExecute Returned errorcode
        /// </summary>
        /// <param name="ReturnCode"></param>
        /// <returns></returns>
        public static string GetShellExecuteReturnMsg(EShellExecReturns ReturnCode)
        {
            try
            {
                switch (ReturnCode)
                {
                    case EShellExecReturns.SE_ERR_ACCESSDENIED: return ShellExecErrorMessages.SE_ERR_ACCESSDENIED;
                    case EShellExecReturns.SE_ERR_ASSOCINCOMPLETE: return ShellExecErrorMessages.SE_ERR_ASSOCINCOMPLETE;
                    case EShellExecReturns.SE_ERR_DDEBUSY: return ShellExecErrorMessages.SE_ERR_DDEBUSY;
                    case EShellExecReturns.SE_ERR_DDEFAIL: return ShellExecErrorMessages.SE_ERR_DDEFAIL;
                    case EShellExecReturns.SE_ERR_DDETIMEOUT: return ShellExecErrorMessages.SE_ERR_DDETIMEOUT;
                    case EShellExecReturns.SE_ERR_DLLNOTFOUND: return ShellExecErrorMessages.SE_ERR_DLLNOTFOUND;
                    case EShellExecReturns.SE_ERR_FNF: return ShellExecErrorMessages.SE_ERR_FNF;
                    case EShellExecReturns.SE_ERR_NOASSOC: return ShellExecErrorMessages.SE_ERR_NOASSOC;
                    case EShellExecReturns.SE_ERR_OOM: return ShellExecErrorMessages.SE_ERR_OOM;
                    case EShellExecReturns.SE_ERR_PNF: return ShellExecErrorMessages.SE_ERR_PNF;
                    case EShellExecReturns.SE_ERR_SHARE: return ShellExecErrorMessages.SE_ERR_SHARE;
                    default: return "Unknown errorcode.";
                }
            }
            catch //(Exception ex)
            {
                return "Unknown errorcode.";
            }
        }

        public static IMalloc GetMalloc()
        {
            IntPtr ptrRet;
            ShellAPI.SHGetMalloc(out ptrRet);

            Object obj = Marshal.GetTypedObjectForIUnknown(ptrRet, GetMallocType());
            IMalloc imalloc = (IMalloc)obj;

            return imalloc;
        }

        public static Type GetMallocType()
        {
            System.Type mallocType = System.Type.GetType("WMSSoft.Lib.Shell.IMalloc");
            return mallocType;
        }

        public static Type GetFolderFilterType()
        {
            System.Type folderFilterType = System.Type.GetType("WMSSoft.Lib.Shell.IFolderFilter");
            return folderFilterType;
        }

        public static Type GetShellFolderType()
        {
            System.Type shellFolderType = System.Type.GetType("WMSSoft.Lib.Shell.IShellFolder");
            return shellFolderType;
        }

        public static IShellFolder GetDesktopFolder()
        {
            IntPtr ptrRet;
            ShellAPI.SHGetDesktopFolder(out ptrRet);

            System.Type shellFolderType = System.Type.GetType("WMSSoft.Lib.Shell.IShellFolder");
            Object obj = Marshal.GetTypedObjectForIUnknown(ptrRet, shellFolderType);
            IShellFolder ishellFolder = (IShellFolder)obj;

            return ishellFolder;
        }

        public static void ShowCustomContextMenu(IntPtr Handle, int LeftStartPos, int TopStartPos, Dictionary<int, string> Items)
        {
            try
            {
                IntPtr myContextMenu = ShellAPI.CreatePopupMenu();

                //Read the Pop-up - Items for File-system - Object
                //iContextMenu.QueryContextMenu(myContextMenu, 0, ShellAPI.CMD_FIRST, ShellAPI.CMD_LAST, CMF.EXTENDEDVERBS | CMF.EXPLORE | CMF.NORMAL);

                if (Items != null)
                {
                    Dictionary<int, string>.KeyCollection MenuIDs = Items.Keys;
                    MENUITEMINFO mii;
                    foreach (int MenuID in MenuIDs)
                    {
                        //Create additional context-menu item


                        mii = new MENUITEMINFO();
                        mii.cbSize = (int)Marshal.SizeOf(mii);
                        mii.fMask = Shell.EMIIM.STRING | Shell.EMIIM.ID; //| Shell.EMIIM.TYPE | Shell.EMIIM.BITMAP | Shell.EMIIM.STATE
                        mii.wID = (uint)MenuID; // 500; //ShellAPI.CMD_FIRST;// + IDM_DISPLAY;//Application ID for this menuItem
                        mii.fType = Shell.EMFT.BYCOMMAND;//D  MFT.MFT_STRING;
                        mii.dwItemData = IntPtr.Zero;
                        mii.dwTypeData = WMSSoft.Lib.Basic.LanguageStrings.GetText(Items[MenuID]);// "Add to Favorites");
                        mii.fState = Shell.EMFS.DEFAULT;
                        //mii.hbmpItem = IntPtr.Zero; //this.menuBmp;
                        //ShellAPI.InsertMenuItem(myContextMenu, (uint)(ShellAPI.GetMenuItemCount(myContextMenu) + 1), true, ref mii);
                        ShellAPI.InsertMenuItem(myContextMenu, 0, true, ref mii);

                    }

                }
                //Show Contextmenu
                uint nSelected = ShellAPI.TrackPopupMenuEx(myContextMenu, TPM.RETURNCMD, LeftStartPos, TopStartPos, Handle, IntPtr.Zero);

                if (nSelected != 0)
                {
                    if (nSelected >= 500)
                    {
                        if (ContextMenuActivated != null) ContextMenuActivated("", (int)nSelected);
                    }
                }
            }
            catch (Exception Ex)
            {
                throw new Exception("Can't show Custom Context-Menu!", Ex);
            }
        }

        public static void ShowContextMenu(string File, IntPtr Handle, int LeftStartPos, int TopStartPos, Dictionary<int,string> Items = null)
        {
            
            IMalloc pMalloc = Shell_helpers.GetMalloc();;
            try
            {      
                IntPtr pidl;
                uint sfgao;
                //first, we convert the given filesysstem path to a pidl (path ID list)
                ShellAPI.SHParseDisplayName(File, IntPtr.Zero, out pidl, 0, out sfgao);              

                IntPtr ptrParent;
                IntPtr pidlRelative = IntPtr.Zero;

                int myBindToParentReturn = 0;
                //
                myBindToParentReturn = ShellAPI.SHBindToParent(pidl, ref ShellAPI.IID_IShellFolder, out ptrParent, out pidlRelative);
                
                //Get a ShellFolder - Object
                System.Type shellFolderType = GetShellFolderType();
                IShellFolder ishellParent = (IShellFolder)System.Runtime.InteropServices.Marshal.GetTypedObjectForIUnknown(ptrParent, shellFolderType);
                
                IContextMenu iContextMenu;

                IntPtr iContextMenuPtr = IntPtr.Zero;
                IntPtr iContextMenu2Ptr = IntPtr.Zero;
                IntPtr iContextMenu3Ptr = IntPtr.Zero;
                IntPtr rgfReserved = IntPtr.Zero;
                //Bind the ShellFolder - Object to the given File-system - Object
                if (ishellParent.GetUIObjectOf(IntPtr.Zero, 1, new IntPtr[] { pidlRelative }, ref ShellAPI.IID_IContextMenu, IntPtr.Zero, out iContextMenuPtr) == ShellAPI.S_OK)
                {
                    iContextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr, typeof(IContextMenu));
                    //Create a new Pop-up - Window used for Contextmenuitems
                    IntPtr myContextMenu = ShellAPI.CreatePopupMenu();
                    try
                    {
                        //Read the Pop-up - Items for File-system - Object
                        iContextMenu.QueryContextMenu(myContextMenu, 0, ShellAPI.CMD_FIRST, ShellAPI.CMD_LAST, CMF.EXTENDEDVERBS | CMF.EXPLORE | CMF.NORMAL );

                        if (Items != null)
                        {
                            Dictionary<int, string>.KeyCollection MenuIDs = Items.Keys;
                            MENUITEMINFO mii;
                            foreach (int MenuID in MenuIDs)
                            {
                                //Create additional context-menu item


                                mii = new MENUITEMINFO();
                                mii.cbSize = (int)Marshal.SizeOf(mii);
                                mii.fMask = Shell.EMIIM.STRING | Shell.EMIIM.ID; //| Shell.EMIIM.TYPE | Shell.EMIIM.BITMAP | Shell.EMIIM.STATE
                                mii.wID = (uint)MenuID; // 500; //ShellAPI.CMD_FIRST;// + IDM_DISPLAY;//Application ID for this menuItem
                                mii.fType = Shell.EMFT.BYCOMMAND;//D  MFT.MFT_STRING;
                                mii.dwItemData = IntPtr.Zero;
                                mii.dwTypeData = WMSSoft.Lib.Basic.LanguageStrings.GetText(Items[MenuID]);// "Add to Favorites");
                                mii.fState = Shell.EMFS.DEFAULT;
                                //mii.hbmpItem = IntPtr.Zero; //this.menuBmp;
                                //ShellAPI.InsertMenuItem(myContextMenu, (uint)(ShellAPI.GetMenuItemCount(myContextMenu) + 1), true, ref mii);
                                ShellAPI.InsertMenuItem(myContextMenu, 0, true, ref mii);

                            }

                        }
                        //Show Contextmenu
                        uint nSelected = ShellAPI.TrackPopupMenuEx(myContextMenu, TPM.RETURNCMD, LeftStartPos, TopStartPos, Handle, IntPtr.Zero);                        

                        Marshal.QueryInterface(iContextMenuPtr, ref ShellAPI.IID_IContextMenu2, out iContextMenu2Ptr);
                        Marshal.QueryInterface(iContextMenuPtr, ref ShellAPI.IID_IContextMenu3, out iContextMenu3Ptr);

                        _iContextMenu2 = (IContextMenu2)Marshal.GetTypedObjectForIUnknown(iContextMenu2Ptr, typeof(IContextMenu2));
                        _iContextMenu3 = (IContextMenu3)Marshal.GetTypedObjectForIUnknown(iContextMenu3Ptr, typeof(IContextMenu3));

                        if (nSelected != 0)
                        {
                            if (nSelected >= 500)
                            {
                                if (ContextMenuActivated != null) ContextMenuActivated(File, (int)nSelected);
                            }
                            else
                                InvokeCommand(iContextMenu, nSelected, File, new Shell.Win32Point(LeftStartPos, TopStartPos));
                        }
                    }
                    finally
                    {
                        ShellAPI.DestroyMenu(myContextMenu);
                        myContextMenu = IntPtr.Zero;

                        if (iContextMenuPtr != IntPtr.Zero)
                            Marshal.Release(iContextMenuPtr);

                        if (iContextMenu2Ptr != IntPtr.Zero)
                            Marshal.Release(iContextMenu2Ptr);

                        if (iContextMenu3Ptr != IntPtr.Zero)
                            Marshal.Release(iContextMenu3Ptr);

                        if (null != iContextMenu)
                        {
                            Marshal.ReleaseComObject(iContextMenu);
                            iContextMenu = null;
                        }
                        if (null != _iContextMenu2)
                        {
                            Marshal.ReleaseComObject(_iContextMenu2);
                            _iContextMenu2 = null;
                        }
                        if (null != _iContextMenu3)
                        {
                            Marshal.ReleaseComObject(_iContextMenu3);
                            _iContextMenu3 = null;
                        }
                        if (null != ishellParent)
                        {
                            Marshal.ReleaseComObject(ishellParent);
                            ishellParent = null;
                        }
                        //if (null != pidlRelative)
                        //{
                        //    Marshal.FreeCoTaskMem(pidlRelative);
                        //    pidlRelative = IntPtr.Zero;
                        //}
                    }
                }                
            }
            catch (Exception Ex)
            {
                throw new Exception("Error on Show ShellContextmenu!", Ex);
            }
            finally
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(pMalloc);
            }
        }

        #region "Registry string redirection"

        /// <summary>
        ///   Retrieves the multilingual string associated with the specified name. Returns null if the name/value pair does not exist in the registry.
        ///   The key must have been opened using 
        /// </summary>
        /// <param name = "key">The registry key to load the string from.</param>
        /// <param name = "name">The name of the string to load.</param>
        /// <returns>The language-specific string, or null if the name/value pair does not exist in the registry.</returns>
        public static string LoadMuiStringValue(this RegistryKey key, string name)
        {
            const int initialBufferSize = 1024;
            var output = new StringBuilder(initialBufferSize);
            int requiredSize;
            IntPtr keyHandle = key.Handle.DangerousGetHandle();
            ShellAPI.ErrorCode result = (ShellAPI.ErrorCode)ShellAPI.RegLoadMUIString(keyHandle, name, output, output.Capacity, out requiredSize, ShellAPI.RegistryLoadMuiStringOptions.None, null);

            if (result == ShellAPI.ErrorCode.MoreData)
            {
                output.EnsureCapacity(requiredSize);
                result = (ShellAPI.ErrorCode)ShellAPI.RegLoadMUIString(keyHandle, name, output, output.Capacity, out requiredSize, ShellAPI.RegistryLoadMuiStringOptions.None, null);
            }

            return result == ShellAPI.ErrorCode.Success ? output.ToString() : null;
        }

        #endregion

        #region "Registry DefaultIcon"
        

        public static System.Drawing.Icon LoadRegistryDefaultIcon(RegistryKey Key)
        {
            try
            {
                RegistryKey tmpKey = null;
                string sDefaultIcon = "";

                if (Key != null)
                {
                    //tmpKey = Key;
                    tmpKey = Key.OpenSubKey("DefaultIcon");

                    if (tmpKey != null)
                    {
                        sDefaultIcon = (string)tmpKey.GetValue("");
                        //sDefaultIcon = Shell.Shell_helpers.LoadMuiStringValue(tmpKey, "");

                        if (sDefaultIcon != null && sDefaultIcon != string.Empty)
                        {
                            string name = "";
                            int icoIndex = -1;
                            int index = sDefaultIcon.LastIndexOf(',');

                            if (index > 0)
                            {
                                name = sDefaultIcon.Substring(0, index);

                                if (index < sDefaultIcon.Length - 1)
                                {
                                    icoIndex = Convert.ToInt32(sDefaultIcon.Substring(index + 1));
                                }
                            }//index
                            else
                                name = sDefaultIcon;

                            IntPtr[] hIconEx = null;//new IntPtr[1] { IntPtr.Zero };
                            IntPtr hIcon = IntPtr.Zero;
                            IntPtr[] hIconSmall = null;
                            int iCount = 0;
                            if (icoIndex < 0)
                            {
                                if (icoIndex == -1)
                                    icoIndex = 0;
                                else
                                {

                                    //string sColIndex = Convert.ToString(icoIndex);
                                    //sColIndex = sColIndex.Trim();
                                    //sColIndex = sColIndex.Substring(1, sColIndex.Length - 1);
                                    //icoIndex = Convert.ToInt32(sColIndex);
                                }
                                
                            }

                            //System.Reflection.Module[] mod = System.Reflection.Assembly.GetCallingAssembly().GetModules();
                            //IntPtr ptr = ShellAPI.ExtractAssociatedIcon(Marshal.GetHINSTANCE(mod[0]), name, ref icoIndex);
                            //iCount = ShellAPI.ExtractIconEx(name, icoIndex, hIconEx, hIconSmall, 1);
                            hIcon = ShellAPI.ExtractIcon(IntPtr.Zero, name, icoIndex);

                            //hIconEx = new IntPtr[iCount];
                            //hIconSmall = new IntPtr[iCount];

                            //iCount = ShellAPI.ExtractIconEx(name, icoIndex, hIconEx, hIconSmall, 1);
                            //if (hIconEx != null && hIconEx[0] != IntPtr.Zero)
                            if ( hIcon != IntPtr.Zero)
                            {
                                //return System.Drawing.Icon.FromHandle(hIconEx[0]);
                                return System.Drawing.Icon.FromHandle(hIcon);
                            }
                        }
                    }
                }
            }
            catch(Exception Ex)
            {
                throw new Exception("Can't load Default Icon from Registry!",Ex) ;
            }
            return null;
        }
        #endregion

        private static void InvokeCommand(IContextMenu oContextMenu, uint nCmd, string strFolder, Shell.Win32Point pointInvoke)
        {
            try
            {
                CMINVOKECOMMANDINFOEX invoke = new CMINVOKECOMMANDINFOEX();
                invoke.cbSize = cbInvokeCommand;
                invoke.lpVerb = (IntPtr)(nCmd - ShellAPI.CMD_FIRST);
                invoke.lpDirectory = strFolder;
                invoke.lpVerbW = (IntPtr)(nCmd - ShellAPI.CMD_FIRST);
                invoke.lpDirectoryW = strFolder;
                invoke.fMask = CMIC.UNICODE | CMIC.PTINVOKE;
                invoke.ptInvoke = new Shell.Win32Point(pointInvoke.x, pointInvoke.y);
                invoke.nShow = Shell.EShowWindowFlags.SW_SHOWNORMAL;

                oContextMenu.InvokeCommand(ref invoke);
            }
            catch (Exception ex) {
                throw new Exception("Error while executing Contextmenuitem!", ex);
            }
        }

        private static void InvokeAddFavorite(string Link, int LinkType)
        {           
                if (AddFavorite != null) AddFavorite(Link, LinkType);
        }

        private static void InvokeRemoveFavorite(string Link, int LinkType)
        {
            if (DeleteFavorite != null) DeleteFavorite(Link, LinkType);
        }

    }
}
